package cc.creativecomputing.resonate;

import cc.creativecomputing.CCApp;
import cc.creativecomputing.CCApplicationManager;
import cc.creativecomputing.control.CCControl;
import cc.creativecomputing.graphics.CCDrawMode;
import cc.creativecomputing.graphics.CCGraphics.CCBlendMode;
import cc.creativecomputing.graphics.CCVBOMesh;
import cc.creativecomputing.graphics.shader.CCGLSLShader;
import cc.creativecomputing.graphics.shader.CCShaderBuffer;
import cc.creativecomputing.graphics.texture.CCTexture2D;
import cc.creativecomputing.graphics.texture.CCTextureIO;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureTarget;
import cc.creativecomputing.io.CCIOUtil;
import cc.creativecomputing.math.CCMath;

/**
 * heightmap constraint
 * 
 * @author maxg
 *
 */
public class ParticleAppForceConstraints_02b extends CCApp{
	
	private CCShaderBuffer _myDataBuffer;
	private CCShaderBuffer _myLastDataBuffer;
	
	@CCControl(name="emit probability", min=0, max=1)
	private float _cEmitProb = 0;
	
	@CCControl(name="constraint resilience", min=0, max=1)
	private float _cResilience = 0;
	
	
	
	// heightmap texture for constraint
	private CCTexture2D _myConstraintTexture;
	
	// same emitter as in example 01
	private CCGLSLShader   _myEmitShader;
	
	// shader to update the velocities, aka force shader
	private CCGLSLShader   _myVelocityShader;
	
	// this shader is needed to ....
	private CCGLSLShader   _myThruShader;
	
	// update shader to calculate displacement under respect of constraints
	private CCGLSLShader   _myUpdateShader;
	
	// this shader is needed to write the points p and n into the constraint fbo
	private CCGLSLShader   _myInitValueShader;
	
	// same as in example 01
	private CCGLSLShader   _myDisplayShader;
	
	private CCVBOMesh _myMesh;
	
	private int particleW = 1000;
	private int particleH = 1000;
	
	@Override
	public void setup() {
		
		addControls("app", "app", this);
		
		_myEmitShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader02/emit_rect_2_fp.glsl"));
		_myEmitShader.load();
		
		_myDisplayShader = new CCGLSLShader (CCIOUtil.classPath(this, "shader02/display_vp.glsl"), 
				  							 CCIOUtil.classPath(this, "shader02/display_fp.glsl"));
		_myDisplayShader.load();
		
		_myVelocityShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader02/force_fp.glsl"));
		_myVelocityShader.load();
		
		_myThruShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader02/thru_fp.glsl"));
		_myThruShader.load();

		_myUpdateShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader02/update_constraints_texture_fp.glsl"));
		_myUpdateShader.load();
		
		_myInitValueShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader02/init_fp.glsl"));
		_myInitValueShader.load();
		
		_myMesh = new CCVBOMesh(CCDrawMode.POINTS, particleW*particleH);
		for (int i=0; i<particleW; i++) {
			for (int j=0; j<particleH; j++) {
				_myMesh.addVertex((float)i/particleW, (float)j/particleH);
			}
		}
		
		_myDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myDataBuffer.clear();
		_myLastDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myLastDataBuffer.clear();
	
		_myConstraintTexture = new CCTexture2D (CCTextureIO.newTextureData("heightmap02.png"));
		g.pointSize(1);
		g.noBlend();
		
	}

	private void swap() {
		CCShaderBuffer tmp = _myDataBuffer;
		_myDataBuffer = _myLastDataBuffer;
		_myLastDataBuffer = tmp;
	}
	
	@Override
	public void update (float theDeltaTime) {
		
		_myEmitShader.start();

		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		
		_myEmitShader.uniform1i("positions", 0);
		_myEmitShader.uniform1i("velocities", 1);
		_myEmitShader.uniform1i("infos", 2);
		_myEmitShader.uniform3f("randomSeed", CCMath.random(100f), CCMath.random(100f),  CCMath.random(3000,10000));
		_myEmitShader.uniform1f("emitProb", _cEmitProb);
		
		_myDataBuffer.draw();
		_myEmitShader.end();
		g.noTexture();
		
		swap();
		
		
		// in this shader stage we pass thru all particle data into the actual data buffer
		// because the following velocity shader stage only writes to velocity texture
		// we need to do this step in order to have the other textures before swapping
		_myThruShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		_myThruShader.uniform1i("positions", 0);
		_myThruShader.uniform1i("velocities", 1);
		_myThruShader.uniform1i("infos", 2);
		
		_myDataBuffer.draw();
		_myThruShader.end();
		g.noTexture();
		
		// velocity shader, write only to velocity texture
		_myVelocityShader.start();
		g.texture (0, _myLastDataBuffer.attachment(1));
		_myVelocityShader.uniform1i("velocities", 0);
		_myDataBuffer.draw(1);
		_myVelocityShader.end();
		g.noTexture();
		
		swap();
		
		// update stage with constraints
		_myUpdateShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		g.texture (3, _myConstraintTexture);
		
		_myUpdateShader.uniform1i("positions", 0);
		_myUpdateShader.uniform1i("velocities", 1);
		_myUpdateShader.uniform1i("infos", 2);
		_myUpdateShader.uniform1i("heightMap", 3);
		
		_myUpdateShader.uniform1f("deltaTime", theDeltaTime);
		_myUpdateShader.uniform1f("resilience", _cResilience);
		_myUpdateShader.uniform1i("dataW", _myConstraintTexture.width());
		_myUpdateShader.uniform1i("dataH", _myConstraintTexture.height());
		_myUpdateShader.uniform3f("scale", 1f,150f,1f);	
		
		_myDataBuffer.draw();
		_myUpdateShader.end();
		g.noTexture();
		
		swap();
	}
	
	@Override
	public void draw() {
		g.pushAttribute();
		g.clearColor(0f);
		g.pointSize(1);
		g.clear();
		g.blend(CCBlendMode.ALPHA);
		_myDisplayShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		_myDisplayShader.uniform1i("positions", 0);
		_myMesh.draw(g);
		_myDisplayShader.end();
		g.noTexture();
		g.popAttribute();
	}
	
	public static void main (String[] args) {
		CCApplicationManager myManager = new CCApplicationManager(ParticleAppForceConstraints_02b.class);
		myManager.settings().size(1000, 1000);
		myManager.settings().display(0);
		myManager.settings().antialiasing(8);
		myManager.settings().fov(30);
		myManager.start();
	}
}
